//
//  ChatQuery.swift
//
//
//  Created by Sergii Kryvoblotskyi on 02/04/2023.
//

import Foundation

/// Creates a model response for the given chat conversation
/// https://platform.openai.com/docs/guides/text-generation
public struct ChatQuery: Equatable, Codable, Streamable, Sendable {
    
    /// A list of messages comprising the conversation so far. Depending on the [model](https://platform.openai.com/docs/models) you use, different message types (modalities) are supported, like [text](https://platform.openai.com/docs/guides/text-generation), [images](https://platform.openai.com/docs/guides/vision), and [audio](https://platform.openai.com/docs/guides/audio).
    public let messages: [Self.ChatCompletionMessageParam]
    /// Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a wide range of models with different capabilities, performance characteristics, and price points. Refer to the [model guide](https://platform.openai.com/docs/models) to browse and compare available models.
    public let model: Model
    /// Constrains effort on reasoning for [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently supported values are none, minimal, low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
    ///
    /// - Note: o-series models only
    public let reasoningEffort: ReasoningEffort?
    
    /// Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.
    /// Defaults to 0
    /// https://platform.openai.com/docs/guides/text-generation/parameter-details
    public let frequencyPenalty: Double?
    
    /// Modify the likelihood of specified tokens appearing in the completion.
    ///
    /// Accepts a JSON object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.
    /// Defaults to null
    public let logitBias: [String:Int]?
    
    /// Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the `content` of `message`.
    ///
    /// Defaults to false
    public let logprobs: Bool?
    
    /// An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.
    ///
    /// See more about [reasoning tokens](https://platform.openai.com/docs/guides/reasoning)
    public let maxCompletionTokens: Int?
    
    /// The maximum number of tokens to generate in the completion.
    /// The total length of input tokens and generated tokens is limited by the model's context length.
    /// https://platform.openai.com/tokenizer
    @available(*, deprecated, message: "This value is now deprecated in favor of max_completion_tokens, and is not compatible with o-series models.")
    public var maxTokens: Int? = nil
    
    /// Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format, and querying for objects via API or the dashboard.
    ///
    /// Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters.
    public let metadata: [String: String]?
    
    /// How many chat completion choices to generate for each input message.
    ///
    /// Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.
    ///
    /// Defaults to 1
    public let n: Int?
    
    /// Whether to enable [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use.
    ///
    /// Defaults to `true`
    public let parallelToolCalls: Bool?
    
    /// Configuration for a Predicted Output, which can greatly improve response times when large parts of the model response are known ahead of time. This is most common when you are regenerating a file with only minor changes to most of the content.
    public let prediction: PredictedOutputConfig?
    /// Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.
    ///
    /// Defaults to 0
    public let presencePenalty: Double?
    
    /// An object specifying the format that the model must output.
    ///
    /// Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs).
    ///
    /// Setting to `{ "type": "json_object" }` enables the older JSON mode, which ensures the message the model generates is valid JSON. Using `json_schema` is preferred for models that support it.
    public let responseFormat: Self.ResponseFormat?
    
    /// This feature is in Beta. If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result. Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.
    public let seed: Int? // BETA
    
    /// Specifies the latency tier to use for processing the request.
    ///
    /// This parameter is relevant for customers subscribed to the scale tier service:
    /// * If set to 'auto', and the Project is Scale tier enabled, the system will utilize scale tier credits until they are exhausted.
    /// * If set to 'auto', and the Project is not Scale tier enabled, the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee.
    /// * If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee.
    /// * If set to 'flex', the request will be processed with the Flex Processing service tier. [Learn more](https://platform.openai.com/docs/guides/flex-processing).
    /// * When not set, the default behavior is 'auto'.
    ///
    /// When this parameter is set, the response body will include the `service_tier` utilized.
    public let serviceTier: ServiceTier?
    
    /// Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence.
    ///
    /// Defaults to null
    ///
    /// - Note: Not supported with latest reasoning models o3 and o4-mini.
    public let stop: Stop?
    
    /// Whether or not to store the output of this chat completion request for use in our [model distillation](https://platform.openai.com/docs/guides/distillation) or [evals](https://platform.openai.com/docs/guides/evals) products.
    ///
    /// Defaults to `false`
    public let store: Bool?
    
    /// What sampling temperature to use, between 0 and 2.
    ///
    /// Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both.
    ///
    /// Defaults to 1
    public let temperature: Double?
    
    /// Controls which (if any) function is called by the model.
    ///
    /// `none` means the model will not call a function and instead generates a message. `auto` means the model can pick between generating a message or calling a function. `required` means the model must call one or more tools. Specifying a particular function via `{"type": "function", "function": {"name": "my_function"}}` forces the model to call that function.
    ///
    /// `none` is the default when no functions are present. `auto` is the default if functions are present
    public let toolChoice: Self.ChatCompletionFunctionCallOptionParam?
    
    /// A list of tools the model may call.
    ///
    /// Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.
    public let tools: [Self.ChatCompletionToolParam]?
    
    /// An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. `logprobs` must be set to `true` if this parameter is used.
    public let topLogprobs: Int?
    
    /// An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.
    ///
    /// We generally recommend altering this or `temperature` but not both.
    ///
    /// Defaults to 1
    public let topP: Double?
    
    /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
    /// [Learn more.](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids)
    public let user: String?
    
    /// This tool searches the web for relevant results to use in a response. Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search).
    public let webSearchOptions: WebSearchOptions?
    
    /// If set to true, the model response data will be streamed to the client as it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). See the [Streaming section](https://platform.openai.com/docs/api-reference/chat/streaming) for more information, along with the [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) guide for more information on how to handle the streaming events.
    ///
    /// Defaults to `false`
    public var stream: Bool
    
    /// Options for streaming response. Only set this when you set `stream: true`.
    ///
    /// Defaults to null
    public var streamOptions: Self.StreamOptions?
    
    /// Parameters for audio output. Required when audio output is requested with modalities: ["audio"].
    public var audioOptions: Self.AudioOptions?
    
    /// Output types that you would like the model to generate.
    ///
    /// Most models are capable of generating text, which is the default: `["text"]`.
    ///
    /// The gpt-4o-audio-preview model can also be used to [generate audio](https://platform.openai.com/docs/guides/audio). To request that this model generate both text and audio responses, you can use: `["text", "audio"]`
    public var modalities: [Self.ChatCompletionModalities]?
    
    public init(
        messages: [Self.ChatCompletionMessageParam],
        model: Model,
        modalities: [Self.ChatCompletionModalities]? = nil,
        audioOptions: Self.AudioOptions? = nil,
        reasoningEffort: ReasoningEffort? = nil,
        frequencyPenalty: Double? = nil,
        logitBias: [String : Int]? = nil,
        logprobs: Bool? = nil,
        maxCompletionTokens: Int? = nil,
        metadata: [String: String]? = nil,
        n: Int? = nil,
        parallelToolCalls: Bool? = nil,
        prediction: PredictedOutputConfig? = nil,
        presencePenalty: Double? = nil,
        responseFormat: Self.ResponseFormat? = nil,
        seed: Int? = nil,
        serviceTier: ServiceTier? = nil,
        stop: Self.Stop? = nil,
        store: Bool? = nil,
        temperature: Double? = nil,
        toolChoice: Self.ChatCompletionFunctionCallOptionParam? = nil,
        tools: [Self.ChatCompletionToolParam]? = nil,
        topLogprobs: Int? = nil,
        topP: Double? = nil,
        user: String? = nil,
        webSearchOptions: WebSearchOptions? = nil,
        stream: Bool = false,
        streamOptions: StreamOptions? = nil
    ) {
        self.messages = messages
        self.model = model
        self.reasoningEffort = reasoningEffort
        self.frequencyPenalty = frequencyPenalty
        self.logitBias = logitBias
        self.logprobs = logprobs
        self.maxCompletionTokens = maxCompletionTokens
        self.metadata = metadata
        self.n = n
        self.parallelToolCalls = parallelToolCalls
        self.prediction = prediction
        self.presencePenalty = presencePenalty
        self.responseFormat = responseFormat
        self.seed = seed
        self.serviceTier = serviceTier
        self.stop = stop
        self.store = store
        self.temperature = temperature
        self.toolChoice = toolChoice
        self.tools = tools
        self.topLogprobs = topLogprobs
        self.topP = topP
        self.user = user
        self.webSearchOptions = webSearchOptions
        self.stream = stream
        self.streamOptions = streamOptions
        self.audioOptions = audioOptions
        self.modalities = modalities
    }
    
    public enum ChatCompletionMessageParam: Codable, Equatable, Sendable {
        /// System Message
        ///
        /// Developer-provided instructions that the model should follow, regardless of messages sent by the user. With o1 models and newer, use `developer` messages for this purpose instead.
        case system(Self.SystemMessageParam)
        /// Developer message
        ///
        /// Developer-provided instructions that the model should follow, regardless of messages sent by the user. With o1 models and newer, `developer` messages replace the previous `system` messages.
        case developer(Self.DeveloperMessageParam)
        /// User message
        ///
        /// Messages sent by an end user, containing prompts or additional context information.
        case user(Self.UserMessageParam)
        /// Assistant message
        ///
        /// Messages sent by the model in response to user messages.
        case assistant(Self.AssistantMessageParam)
        /// Tool message
        case tool(Self.ToolMessageParam)
        
        public var role: Role { get {
            switch self {
            case .system(let systemMessage):
                return systemMessage.role
            case .developer(let developerMessage):
                return developerMessage.role
            case .user(let userMessage):
                return userMessage.role
            case .assistant(let assistantMessage):
                return assistantMessage.role
            case .tool(let toolMessage):
                return toolMessage.role
            }
        }}
        
        public var name: String? { get {
            switch self {
            case .system(let systemMessage):
                return systemMessage.name
            case .developer(let developerMessage):
                return developerMessage.name
            case .user(let userMessage):
                return userMessage.name
            case .assistant(let assistantMessage):
                return assistantMessage.name
            default:
                return nil
            }
        }}
        
        public var toolCallId: String? { get {
            switch self {
            case .tool(let toolMessage):
                return toolMessage.toolCallId
            default:
                return nil
            }
        }}
        
        public var toolCalls: [Self.AssistantMessageParam.ToolCallParam]? { get {
            switch self {
            case .assistant(let assistantMessage):
                return assistantMessage.toolCalls
            default:
                return nil
            }
        }}
        
        public init?(
            role: Role,
            content: String? = nil,
            imageData: Data? = nil,
            name: String? = nil,
            toolCalls: [Self.AssistantMessageParam.ToolCallParam]? = nil,
            toolCallId: String? = nil
        ) {
            switch role {
            case .system:
                if let content {
                    self = .system(.init(content: .textContent(content), name: name))
                } else {
                    return nil
                }
            case .developer:
                if let content {
                    self = .developer(.init(content: .textContent(content), name: name))
                } else {
                    return nil
                }
            case .user:
                if let content {
                    self = .user(.init(content: .string(content), name: name))
                } else {
                    return nil
                }
            case .assistant:
                if let content {
                    self = .assistant(.init(content: .textContent(content), name: name, toolCalls: toolCalls))
                } else {
                    self = .assistant(.init(content: nil, name: name, toolCalls: toolCalls))
                }
            case .tool:
                if let content, let toolCallId {
                    self = .tool(.init(content: .textContent(content), toolCallId: toolCallId))
                } else {
                    return nil
                }
            }
        }
        
        private init?(
            content: String,
            role: Role,
            name: String? = nil
        ) {
            if role == .system {
                self = .system(.init(content: .textContent(content), name: name))
            } else if role == .developer {
                self = .developer(.init(content: .textContent(content), name: name))
            } else {
                return nil
            }
        }
        
        private init?(
            content: Self.UserMessageParam.Content,
            role: Role,
            name: String? = nil
        ) {
            if role == .user {
                self = .user(.init(content: content, name: name))
            } else {
                return nil
            }
        }
        
        private init?(
            role: Role,
            content: String? = nil,
            name: String? = nil,
            toolCalls: [Self.AssistantMessageParam.ToolCallParam]? = nil
        ) {
            if role == .assistant {
                if let content {
                    self = .assistant(.init(content: .textContent(content), name: name, toolCalls: toolCalls))
                } else {
                    self = .assistant(.init(name: name, toolCalls: toolCalls))
                }
            } else {
                return nil
            }
        }
        
        private init?(
            content: String,
            role: Role,
            toolCallId: String
        ) {
            if role == .tool {
                self = .tool(.init(content: .textContent(content), toolCallId: toolCallId))
            } else {
                return nil
            }
        }
        
        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .system(let a0):
                try container.encode(a0)
            case .developer(let a0):
                try container.encode(a0)
            case .user(let a0):
                try container.encode(a0)
            case .assistant(let a0):
                try container.encode(a0)
            case .tool(let a0):
                try container.encode(a0)
            }
        }
        
        enum CodingKeys: CodingKey {
            case system
            case developer
            case user
            case assistant
            case tool
        }
        
        public struct SystemMessageParam: Codable, Equatable, Sendable {
            public typealias Role = ChatQuery.ChatCompletionMessageParam.Role
            
            /// The contents of the system message.
            public let content: TextContent
            /// The role of the messages author, in this case system.
            public let role: Self.Role = .system
            /// An optional name for the participant. Provides the model information to differentiate between participants of the same role.
            public let name: String?
            
            public init(
                content: TextContent,
                name: String? = nil
            ) {
                self.content = content
                self.name = name
            }
            
            enum CodingKeys: CodingKey {
                case content
                case role
                case name
            }
        }
        
        public struct DeveloperMessageParam: Codable, Equatable, Sendable {
            public typealias Role = ChatQuery.ChatCompletionMessageParam.Role
            
            /// The contents of the developer message.
            public let content: TextContent
            /// The role of the messages author, in this case developer.
            public let role: Self.Role = .developer
            /// An optional name for the participant. Provides the model information to differentiate between participants of the same role.
            public let name: String?
            
            public init(
                content: TextContent,
                name: String? = nil
            ) {
                self.content = content
                self.name = name
            }
            
            enum CodingKeys: CodingKey {
                case content
                case role
                case name
            }
        }
        
        public struct UserMessageParam: Codable, Equatable, Sendable {
            public typealias Role = ChatQuery.ChatCompletionMessageParam.Role
            
            /// The contents of the user message.
            public let content: Content
            /// The role of the messages author, in this case `user`.
            public let role: Self.Role = .user
            /// An optional name for the participant. Provides the model information to differentiate between participants of the same role.
            public let name: String?
            
            public init(
                content: Content,
                name: String? = nil
            ) {
                self.content = content
                self.name = name
            }
            
            enum CodingKeys: CodingKey {
                case content
                case role
                case name
            }
            
            public enum Content: Codable, Equatable, Sendable {
                public enum ContentPart: Codable, Hashable, Sendable {
                    /// Learn about [text inputs](https://platform.openai.com/docs/guides/text-generation).
                    case text(ContentPartTextParam)
                    /// Learn about [image inputs](https://platform.openai.com/docs/guides/vision).
                    case image(ContentPartImageParam)
                    /// Learn about [audio inputs](https://platform.openai.com/docs/guides/audio).
                    case audio(ContentPartAudioParam)
                    /// Learn about [file inputs](https://platform.openai.com/docs/guides/text) for text generation.
                    case file(ContentPartFileParam)
                    
                    public init(from decoder: Decoder) throws {
                        let container = try decoder.singleValueContainer()
                        
                        if let value = try? container.decode(ContentPartTextParam.self) {
                            self = .text(value)
                            return
                        }
                        if let value = try? container.decode(ContentPartImageParam.self) {
                            self = .image(value)
                            return
                        }
                        if let value = try? container.decode(ContentPartAudioParam.self) {
                            self = .audio(value)
                            return
                        }
                        if let value = try? container.decode(ContentPartFileParam.self) {
                            self = .file(value)
                            return
                        }
                        
                        throw DecodingError.dataCorruptedError(
                            in: container,
                            debugDescription: "ContentPart could not be decoded into any known type"
                        )
                    }
                    
                    public func encode(to encoder: Encoder) throws {
                        var container = encoder.singleValueContainer()
                        
                        switch self {
                        case .text(let value):
                            try container.encode(value)
                        case .image(let value):
                            try container.encode(value)
                        case .audio(let value):
                            try container.encode(value)
                        case .file(let value):
                            try container.encode(value)
                        }
                    }
                }
                
                case string(String)
                case contentParts([ContentPart])
                
                public var string: String? { get {
                    switch self {
                    case .string(let string):
                        return string
                    default:
                        return nil
                    }
                }}
                
                
                public func encode(to encoder: Encoder) throws {
                    var container = encoder.singleValueContainer()
                    switch self {
                    case .string(let a0):
                        try container.encode(a0)
                    case .contentParts(let parts):
                        try container.encode(parts)
                    }
                }
            }
        }
        
        public enum TextContent: Codable, Equatable, Sendable {
            case textContent(String)
            /// An array of content parts with a defined type. For system, developer messages, only type `text` is supported.
            case contentParts([ContentPartTextParam])
            
            public func encode(to encoder: Encoder) throws {
                var container = encoder.singleValueContainer()
                switch self {
                case .textContent(let a0):
                    try container.encode(a0)
                case .contentParts(let parts):
                    try container.encode(parts)
                }
            }
        }
        
        public enum TextOrRefusalContent: Codable, Equatable, Sendable {
            public enum ContentPart: Codable, Hashable, Sendable {
                /// Learn about [text inputs](https://platform.openai.com/docs/guides/text-generation).
                case text(ContentPartTextParam)
                case refusal(Components.Schemas.RefusalContent)
                
                public init(from decoder: Decoder) throws {
                    let container = try decoder.singleValueContainer()
                    
                    if let value = try? container.decode(ContentPartTextParam.self) {
                        self = .text(value)
                        return
                    }
                    if let value = try? container.decode(Components.Schemas.RefusalContent.self) {
                        self = .refusal(value)
                        return
                    }
                    
                    throw DecodingError.dataCorruptedError(
                        in: container,
                        debugDescription: "ContentPart could not be decoded into any known type"
                    )
                }
                
                public func encode(to encoder: Encoder) throws {
                    var container = encoder.singleValueContainer()
                    
                    switch self {
                    case .text(let value):
                        try container.encode(value)
                    case .refusal(let value):
                        try container.encode(value)
                    }
                }
            }
            
            case textContent(String)
            case contentParts([ContentPart])
            
            public func encode(to encoder: Encoder) throws {
                var container = encoder.singleValueContainer()
                switch self {
                case .textContent(let a0):
                    try container.encode(a0)
                case .contentParts(let parts):
                    try container.encode(parts)
                }
            }
        }
        
        public struct ContentPartTextParam: Codable, Hashable, Sendable {
            /// The text content.
            public let text: String
            /// The type of the content part.
            public let type: String
            
            public init(text: String) {
                self.text = text
                self.type = "text"
            }
        }
        
        public struct ContentPartAudioParam: Codable, Hashable, Sendable {
            public let inputAudio: InputAudio
            /// The type of the content part.
            public let type: String
            
            public init(inputAudio: InputAudio) {
                self.inputAudio = inputAudio
                self.type = "input_audio"
            }
            
            public struct InputAudio: Codable, Hashable, Sendable {
                /// Base64 encoded audio data.
                public let data: String
                /// The format of the encoded audio data. Currently supports "wav" and "mp3".
                public let format: Format
                
                public init(data: String, format: Format) {
                    self.data = data
                    self.format = format
                }
                
                public init(data: Data, format: Format) {
                    self.init(
                        data: data.base64EncodedString(),
                        format: format
                    )
                }
                
                public enum Format: String, Codable, Hashable, CaseIterable, Sendable {
                    case wav
                    case mp3
                }
            }
            
            public enum CodingKeys: String, CodingKey {
                case inputAudio = "input_audio"
                case type
            }
        }
        
        public struct ContentPartImageParam: Codable, Hashable, Sendable {
            public let imageUrl: ImageURL
            /// The type of the content part.
            public let type: String
            
            public init(imageUrl: ImageURL) {
                self.imageUrl = imageUrl
                self.type = "image_url"
            }
            
            public struct ImageURL: Codable, Hashable, Sendable {
                /// Either a URL of the image or the base64 encoded image data.
                public let url: String
                /// Specifies the detail level of the image. Learn more in the
                /// Vision guide https://platform.openai.com/docs/guides/vision/low-or-high-fidelity-image-understanding
                public let detail: Detail?
                
                public init(url: String, detail: Detail?) {
                    self.url = url
                    self.detail = detail
                }
                
                public init(imageData: Data, detail: Detail?) {
                    self.init(
                        url: "data:image/jpeg;base64,\(imageData.base64EncodedString())",
                        detail: detail)
                }
                
                public enum Detail: String, Codable, Hashable, CaseIterable, Sendable {
                    case auto
                    case low
                    case high
                }
            }
            
            public enum CodingKeys: String, CodingKey {
                case imageUrl = "image_url"
                case type
            }
        }
        
        public struct ContentPartFileParam: Codable, Hashable, Sendable {
            public let file: File
            /// The type of the content part. Always "file".
            public let type: String

            public init(file: File) {
                self.file = file
                self.type = "file"
            }

            public struct File: Codable, Hashable, Sendable {
                /// The base64 encoded file data, used when passing the file to the model as a string.
                public let fileData: String?
                /// The ID of an uploaded file to use as input.
                public let fileId: String?
                /// The name of the file, used when passing the file to the model as a string.
                public let filename: String?

                public init(fileData: String? = nil, fileId: String? = nil, filename: String? = nil) {
                    self.fileData = fileData
                    self.fileId = fileId
                    self.filename = filename
                }

                public init(data: Data, filename: String) {
                    self.init(
                        fileData: data.base64EncodedString(),
                        fileId: nil,
                        filename: filename
                    )
                }

                public enum CodingKeys: String, CodingKey {
                    case fileData = "file_data"
                    case fileId = "file_id"
                    case filename
                }
            }

            public enum CodingKeys: String, CodingKey {
                case file
                case type
            }
        }

        internal struct ChatCompletionMessageParam: Codable, Equatable {
            typealias Role = ChatQuery.ChatCompletionMessageParam.Role

            let role: Self.Role

            enum CodingKeys: CodingKey {
                case role
            }
        }

        public struct AssistantMessageParam: Codable, Equatable, Sendable {
            public typealias Role = ChatQuery.ChatCompletionMessageParam.Role

            //// The role of the messages author, in this case assistant.
            public let role: Self.Role = .assistant
            /// The contents of the assistant message. Required unless `tool_calls` is specified.
            public let content: TextOrRefusalContent?
            /// Data about a previous audio response from the model.
            public let audio: Audio?
            /// The name of the author of this message. `name` is required if role is `function`, and it should be the name of the function whose response is in the `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters.
            public let name: String?
            /// The tool calls generated by the model, such as function calls.
            public let toolCalls: [Self.ToolCallParam]?

            public init(
                content: TextOrRefusalContent? = nil,
                audio: Audio? = nil,
                name: String? = nil,
                toolCalls: [Self.ToolCallParam]? = nil
            ) {
                self.content = content
                self.audio = audio
                self.name = name
                self.toolCalls = toolCalls
            }

            public enum CodingKeys: String, CodingKey {
                case name
                case role
                case content
                case audio
                case toolCalls = "tool_calls"
            }

            public struct Audio: Codable, Equatable, Sendable {
                public let id: String

                public init(id: String) {
                    self.id = id
                }
            }

            public struct ToolCallParam: Codable, Equatable, Sendable {
                public typealias ToolsType = ChatQuery.ChatCompletionToolParam.ToolsType

                /// The ID of the tool call.
                public let id: String
                /// The function that the model called.
                public let function: Self.FunctionCall
                /// The type of the tool. Currently, only `function` is supported.
                public let type: Self.ToolsType

                public init(
                    id: String,
                    function:  Self.FunctionCall
                ) {
                    self.id = id
                    self.function = function
                    self.type = .function
                }

                public init(from decoder: any Decoder) throws {
                    let container = try decoder.container(keyedBy: CodingKeys.self)
                    let parsingOptions = decoder.userInfo[.parsingOptions] as? ParsingOptions ?? []
                    // Some API providers for some AI models (e.g. openrouter.ai for Qwen model, mistral.ai for their models) don't return id and type parameters for ToolCallParam.
                    // See https://github.com/MacPaw/OpenAI/pull/366
                    self.id = try container.decodeString(forKey: .id, parsingOptions: parsingOptions)
                    self.function = try container.decode(FunctionCall.self, forKey: .function)
                    self.type = try container.decodeIfPresent(ToolsType.self, forKey: .type) ?? .function
                }

                public struct FunctionCall: Codable, Equatable, Sendable {
                    /// The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function.
                    public let arguments: String
                    /// The name of the function to call.
                    public let name: String

                    public init(
                        arguments: String,
                        name: String
                    ) {
                        self.arguments = arguments
                        self.name = name
                    }
                }
            }
        }

        public struct ToolMessageParam: Codable, Equatable, Sendable {
            public typealias Role = ChatQuery.ChatCompletionMessageParam.Role

            /// The contents of the tool message.
            public let content: TextContent
            /// The role of the messages author, in this case tool.
            public let role: Self.Role = .tool
            /// Tool call that this message is responding to.
            public let toolCallId: String

            public init(
                content: TextContent,
                toolCallId: String
            ) {
                self.content = content
                self.toolCallId = toolCallId
            }

            public enum CodingKeys: String, CodingKey {
                case content
                case role
                case toolCallId = "tool_call_id"
            }
        }

        public enum Role: String, Codable, Equatable, CaseIterable, Sendable {
            case system
            case developer
            case user
            case assistant
            case tool
        }
    }

    public enum Stop: Codable, Equatable, Sendable {
        case string(String)
        case stringList([String])

        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .string(let a0):
                try container.encode(a0)
            case .stringList(let a0):
                try container.encode(a0)
            }
        }

        public init(string: String) {
            self = .string(string)
        }

        public init(stringList: [String]) {
            self = .stringList(stringList)
        }
    }
    
    public enum ReasoningEffort: Codable, Equatable, Sendable {
        case none
        case minimal
        case low
        case medium
        case high
        
        /// Intended for use with other providers except OpenAI
        /// For example, Gemini Flash 2.5 has "reasoning-disabled" mode. A developer would use `ReasoningEffort.customValue("reasoning-disable")` to specify the mode.
        /// See https://github.com/MacPaw/OpenAI/issues/327 for more details about the case
        case customValue(String)
        
        public func encode(to encoder: any Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .none:
                try container.encode("none")
            case .minimal:
                try container.encode("minimal")
            case .low:
                try container.encode("low")
            case .medium:
                try container.encode("medium")
            case .high:
                try container.encode("high")
            case .customValue(let value):
                try container.encode(value)
            }
        }
        
        public init(from decoder: any Decoder) throws {
            let container = try decoder.singleValueContainer()
            let rawValue = try container.decode(String.self)
            switch rawValue {
            case "none":
                self = .none
            case "minimal":
                self = .minimal
            case "low":
                self = .low
            case "medium":
                self = .medium
            case "high":
                self = .high
            default:
                self = .customValue(rawValue)
            }
        }
    }

    // See more https://platform.openai.com/docs/guides/structured-outputs/introduction
    public enum ResponseFormat: Codable, Hashable, Sendable {
        /// Default response format. Used to generate text responses.
        case text
        /// JSON object response format. An older method of generating JSON responses. Using `json_schema` is recommended for models that support it. Note that the model will not generate JSON without a system or user message instructing it to do so.
        case jsonObject
        /// JSON Schema response format. Used to generate structured JSON responses. Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs).
        case jsonSchema(StructuredOutputConfigurationOptions)
        
        enum CodingKeys: String, CodingKey {
            case type
            case jsonSchema = "json_schema"
        }
        
        public func encode(to encoder: any Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            switch self {
            case .text:
                try container.encode("text", forKey: .type)
            case .jsonObject:
                try container.encode("json_object", forKey: .type)
            case .jsonSchema(let options):
                try container.encode("json_schema", forKey: .type)
                try container.encode(options, forKey: .jsonSchema)
            }
        }
        
        public static func == (lhs: ResponseFormat, rhs: ResponseFormat) -> Bool {
            switch (lhs, rhs) {
            case (.text, .text):
                return true
            case (.jsonObject, .jsonObject):
                return true
            case (.jsonSchema(let lhsFormat), .jsonSchema(let rhsFormat)):
                return lhsFormat == rhsFormat
            default:
                return false
            }
        }
        
        /// A formal initializer reqluired for the inherited Decodable conformance.
        /// This type is never returned from the server and is never decoded into.
        public init(from decoder: any Decoder) throws {
            self = .text
        }
    }
    
    public struct StructuredOutputConfigurationOptions: Codable, Hashable, Sendable {
        /// The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
        let name: String
        /// A description of what the response format is for, used by the model to determine how to respond in the format.
        let description: String?
        /// The schema for the response format, described as a JSON Schema object. Learn how to build JSON schemas [here](https://json-schema.org/).
        let schema: JSONSchemaDefinition?
        /// Whether to enable strict schema adherence when generating the output. If set to true, the model will always follow the exact schema defined in the `schema` field. Only a subset of JSON Schema is supported when `strict` is `true`. To learn more, read the [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) guide.
        ///
        /// Defaults to false
        let strict: Bool?
        
        public init(name: String, description: String? = nil, schema: JSONSchemaDefinition? = nil, strict: Bool? = nil) {
            self.name = name
            self.description = description
            self.schema = schema
            self.strict = strict
        }
        
        public init(from decoder: any Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try container.decode(String.self, forKey: .name)
            self.description = try container.decodeIfPresent(String.self, forKey: .description)
            self.strict = try container.decodeIfPresent(Bool.self, forKey: .strict)
            self.schema = try container.decodeIfPresent(JSONSchemaDefinition.self, forKey: .schema)
        }
        
        public func encode(to encoder: any Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(self.name, forKey: CodingKeys.name)
            try container.encodeIfPresent(self.description, forKey: CodingKeys.description)
            try container.encodeIfPresent(self.schema, forKey: CodingKeys.schema)
            try container.encodeIfPresent(self.strict, forKey: CodingKeys.strict)
        }
        
        enum CodingKeys: CodingKey {
            case name
            case description
            case schema
            case strict
        }
        
        public static func == (lhs: ChatQuery.StructuredOutputConfigurationOptions, rhs: ChatQuery.StructuredOutputConfigurationOptions) -> Bool {
            guard lhs.name == rhs.name,
                  lhs.description == rhs.description,
                  lhs.strict == rhs.strict else {
                return false
            }
            
            if let rhsSchema = rhs.schema, let lhsSchema = lhs.schema {
                return rhsSchema == lhsSchema
            } else if rhs.schema == nil, lhs.schema == nil {
                return true
            } else {
                return false
            }
        }
    }

    public enum ChatCompletionFunctionCallOptionParam: Codable, Equatable, Sendable {
        case none
        case auto
        case function(String)
        case required

        public func encode(to encoder: Encoder) throws {
            switch self {
            case .none:
                var container = encoder.singleValueContainer()
                try container.encode(CodingKeys.none.rawValue)
            case .auto:
                var container = encoder.singleValueContainer()
                try container.encode(CodingKeys.auto.rawValue)
            case .function(let name):
                var container = encoder.container(keyedBy: Self.ChatCompletionFunctionCallNameParam.CodingKeys.self)
                try container.encode("function", forKey: .type)
                try container.encode(["name": name], forKey: .function)
            case .required:
                var container = encoder.singleValueContainer()
                try container.encode(CodingKeys.required.rawValue)
            }
        }

        public init(function: String) {
            self = .function(function)
        }

        enum CodingKeys: String, CodingKey {
            case none = "none"
            case auto = "auto"
            case function = "name"
            case required = "required"
        }

        private enum ChatCompletionFunctionCallNameParam: Codable, Equatable {
            case type
            case function

            enum CodingKeys: CodingKey {
                case type
                case function
            }
        }
    }

    public struct ChatCompletionToolParam: Codable, Equatable, Sendable {

        public let function: Self.FunctionDefinition
        /// The type of the tool. Currently, only `function` is supported.
        public let type: Self.ToolsType

        public init(
            function: Self.FunctionDefinition
        ) {
            self.function = function
            self.type = .function
        }

        public struct FunctionDefinition: Codable, Equatable, Sendable {
            /// The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
            public let name: String

            /// The description of what the function does.
            public let description: String?

            /// The parameters the functions accepts, described as a JSON Schema object. See the [guide](https://platform.openai.com/docs/guides/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.
            ///
            /// Omitting `parameters` defines a function with an empty parameter list.
            public let parameters: JSONSchema?
            /// Whether to enable strict schema adherence when generating the function call. If set to true, the model will follow the exact schema defined in the `parameters` field. Only a subset of JSON Schema is supported when `strict` is `true`. Learn more about Structured Outputs in the [function calling guide](https://platform.openai.com/docs/api-reference/chat/docs/guides/function-calling).
            ///
            /// Defaults to false
            public let strict: Bool?

            public init(
                name: String,
                description: String? = nil,
                parameters: JSONSchema? = nil,
                strict: Bool? = nil
            ) {
                self.name = name
                self.description = description
                self.parameters = parameters
                self.strict = strict
            }
        }

        public enum ToolsType: String, Codable, Equatable, Sendable {
            case function
        }
    }
    
    public struct StreamOptions: Codable, Equatable, Sendable {
        /// If set, an additional chunk will be streamed before the `data: [DONE]` message.
        /// The `usage` field on this chunk shows the token usage statistics for the entire request,
        /// and the `choices` field will always be an empty array.
        ///
        /// All other chunks will also include a usage field, but with a null value.
        ///
        /// - Note: If the stream is interrupted, you may not receive the final usage chunk which contains the total token usage for the request.
        public let includeUsage: Bool
        
        public init(includeUsage: Bool) {
            self.includeUsage = includeUsage
        }
        
        public enum CodingKeys: String, CodingKey {
            case includeUsage = "include_usage"
        }
    }

    public struct AudioOptions: Codable, Equatable, Sendable {
        public let format: AudioOptionsResponseFormat
        public let voice: AudioOptionsSpeechVoice

        public enum AudioOptionsResponseFormat: String, Codable, CaseIterable, Sendable {
            case mp3
            case opus
            case flac
            case wav
            case pcm16
        }

        public enum AudioOptionsSpeechVoice: String, Codable, CaseIterable, Sendable {
            case alloy
            case ash
            case ballad
            case coral
            case echo
            case fable
            case onyx
            case nova
            case sage
            case shimmer
            case verse
        }

        public init(
            format: AudioOptionsResponseFormat,
            voice: AudioOptionsSpeechVoice
        ) {
            self.format = format
            self.voice = voice
        }
    }

    public enum ChatCompletionModalities: String, Codable, Equatable, Sendable {
        case text
        case audio
    }
    
    public enum PredictedOutputConfig: Codable, Hashable, Sendable {
        public struct StaticContent: Codable, Hashable, Sendable {
            public enum Content: Codable, Hashable, Sendable {
                public struct ContentPart: Codable, Hashable, Sendable {
                    /// The text content.
                    let text: String
                    /// The type of the content part.
                    let type: String
                }
                
                /// The content used for a Predicted Output. This is often the text of a file you are regenerating with minor changes.
                case textContent(String)
                /// An array of content parts with a defined type. Supported options differ based on the [model](https://platform.openai.com/docs/models) being used to generate the response. Can contain text inputs.
                case contentParts([ContentPart])
                
                public init(from decoder: Decoder) throws {
                    let container = try decoder.singleValueContainer()
                    
                    if let textValue = try? container.decode(String.self) {
                        self = .textContent(textValue)
                        return
                    }
                    
                    if let parts = try? container.decode([ContentPart].self) {
                        self = .contentParts(parts)
                        return
                    }
                    
                    throw DecodingError.dataCorruptedError(
                        in: container,
                        debugDescription: "Unable to decode Content as either textContent or contentParts"
                    )
                }
                
                public func encode(to encoder: Encoder) throws {
                    var container = encoder.singleValueContainer()
                    switch self {
                    case .textContent(let value):
                        try container.encode(value)
                    case .contentParts(let parts):
                        try container.encode(parts)
                    }
                }
            }
            
            /// The content that should be matched when generating a model response. If generated tokens would match this content, the entire model response can be returned much more quickly.
            let content: Content
            /// The type of the predicted content you want to provide. This type is currently always `content`.
            let type: String
            
            init(content: Content, type: String = "content") {
                self.content = content
                self.type = type
            }
        }
        
        /// Static predicted output content, such as the content of a text file that is being regenerated.
        case staticContent(StaticContent)
        
        public init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            
            if let staticContent = try? container.decode(StaticContent.self) {
                self = .staticContent(staticContent)
                return
            }
            
            throw DecodingError.dataCorruptedError(
                in: container,
                debugDescription: "Unable to decode PredictedOutputConfig"
            )
        }
        
        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .staticContent(let value):
                try container.encode(value)
            }
        }
    }

    public enum CodingKeys: String, CodingKey {
        case messages
        case model
        case reasoningEffort = "reasoning_effort"
        case frequencyPenalty = "frequency_penalty"
        case logitBias = "logit_bias"
        case logprobs
        case maxTokens = "max_tokens"
        case maxCompletionTokens = "max_completion_tokens"
        case metadata
        case n
        case parallelToolCalls = "parallel_tool_calls"
        case prediction
        case presencePenalty = "presence_penalty"
        case responseFormat = "response_format"
        case seed
        case serviceTier = "service_tier"
        case stop
        case store
        case temperature
        case toolChoice = "tool_choice"
        case tools
        case topLogprobs = "top_logprobs"
        case topP = "top_p"
        case user
        case webSearchOptions = "web_search_options"
        case stream
        case streamOptions = "stream_options"
        case audioOptions = "audio"
        case modalities = "modalities"
    }
}

public struct ChatFunctionCall: Codable, Equatable, Sendable {
    /// The name of the function to call.
    public let name: String
    /// The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function.
    public let arguments: String

    public init(name: String, arguments: String) {
        self.name = name
        self.arguments = arguments
    }
}

public struct ChatToolCall: Codable, Equatable {
    public enum ToolType: String, Codable, Equatable {
        case function
    }

    public let id: String
    public let type: ToolType
    public let function: ChatFunctionCall

    public init(id: String, type: ToolType = .function, function: ChatFunctionCall) {
        self.id = id
        self.type = type
        self.function = function
    }
}
